package com.googlecode.afdbaccess.web.common.errorhelp;

import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.net.InetAddress;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

/**
 * Generates unique strings that for example can be used as unique ID's. <br />
 * The implementation is not optimised for speed.
 * <ol>
 * <li>Unique down to the millisecond. Digits 1-8 are are the hex encoded lower
 * 32 bits of the System.currentTimeMillis() call.</li>
 * <li>Unique across a cluster. Digits 9-16 are the encoded representation of
 * the 32 bit integer of the underlying IP address.</li>
 * <li>Unique down to the object in a JVM. Digits 17-24 are the hex
 * representation of the call to System.identityHashCode(), which is guaranteed
 * to return distinct integers for distinct objects within a JVM.</li>
 * <li>Unique within an object within a millisecond. Finally digits 25-32
 * represent a random 32 bit integer generated on every method call using the
 * cryptographically strong java.security.SecureRandom class using the SHA1PRNG
 * algorithm.</li>
 * </ol>
 */
public class UniqueIdGenerator {
    private SecureRandom randomGenerator = null;

    /**
     * Get a new unique ID string. The string is always exactly 32 hex
     * characters.
     * 
     * @return A new unique ID string.
     */
    public String getId() {
        StringBuffer idBuffer = new StringBuffer();
        idBuffer.append(getRandomComponent());
        idBuffer.append(getJvmIdentityComponent());
        idBuffer.append(getNetworkComponent());
        idBuffer.append(getTimeComponent());
        return idBuffer.toString();
    }

    private String getTimeComponent() {
        long now = System.currentTimeMillis();
        return forceTo8Chars(Long.toHexString(now));
    }

    private String getNetworkComponent() {
        try {
            InetAddress localAddress = java.net.InetAddress.getLocalHost();
            byte[] rawArray = localAddress.getAddress();
            DataInputStream in = new DataInputStream(new ByteArrayInputStream(
                    rawArray));
            int rawAddress = in.readInt();
            return forceTo8Chars(Integer.toHexString(rawAddress));
        } catch (Exception ex) {
            return "00000000";
        }
    }

    private String getJvmIdentityComponent() {
        String identityString = Integer.toHexString(System
                .identityHashCode(this));
        return forceTo8Chars(identityString);
    }

    private String getRandomComponent() {
        int randomNumber;
        if (randomGenerator == null) {
            try {
                randomGenerator = SecureRandom.getInstance("SHA1PRNG");
            } catch (NoSuchAlgorithmException ex) {
                randomGenerator = null;
            }
        }
        if (randomGenerator == null) {
            randomNumber = (int) Math.random();
        } else {
            randomNumber = randomGenerator.nextInt();
        }
        return forceTo8Chars(Integer.toHexString(randomNumber));
    }

    private String forceTo8Chars(String value) {
        String newValue = value;
        if (newValue.length() > 8) {
            newValue = newValue.substring(newValue.length() - 8);
        }
        if (newValue.length() < 8) {
            newValue = pad(newValue, 8);
        }
        return newValue;
    }

    private String pad(String valueToPad, int lengthOfResult) {
        StringBuffer result = new StringBuffer();
        for (int n = lengthOfResult - valueToPad.length(); n > 0; n--) {
            result.append("0");
        }
        result.append(valueToPad);
        return result.toString();
    }

//    public static void main(String[] args) {
//        UniqueIdGenerator uniqueIDGenerator = new UniqueIdGenerator();
//        long startTime = System.currentTimeMillis();
//        for (int n = 0; n < 1000; n++) {
//            uniqueIDGenerator.getID();
//        }
//        long time = System.currentTimeMillis() - startTime;
//        System.out.println("1000 GUIDS generated in " + time + " ms");
//        System.out.println("Avarage " + time / 1000 + " ms / GUID");
//    }
}